package com.feiya.pianoscore;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.AssetManager;
import android.graphics.Color;
import android.util.Log;
import android.view.View;

import com.feiya.pianoscore.ScoreView.FM_Accidental;
import com.feiya.pianoscore.ScoreView.FM_Align;
import com.feiya.pianoscore.ScoreView.FM_BaseNote;
import com.feiya.pianoscore.ScoreView.FM_Chord;
import com.feiya.pianoscore.ScoreView.FM_ClefValue;
import com.feiya.pianoscore.ScoreView.FM_DurationValue;
import com.feiya.pianoscore.ScoreView.FM_KeySignatureValue;
import com.feiya.pianoscore.ScoreView.FM_Note;
import com.feiya.pianoscore.ScoreView.FM_NoteValue;
import com.feiya.pianoscore.ScoreView.FM_Score;
import com.feiya.pianoscore.ScoreView.FM_ScorePlayer;
import com.feiya.pianoscore.ScoreView.FM_StaveCount;
import com.feiya.pianoscore.ScoreView.FM_TimeSignatureValue;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ScoreViewController
{
    final static String TAG = Constants.TAG;
    FM_Score scoreView;
    FM_ScorePlayer player;
    //Activity context;

    public ScoreViewController(Activity context){
        //this.context = context;
        FM_Score s = context.findViewById(R.id.stave);
        s.setBackgroundColor(Color.argb(255, 224, 211, 175));
        s.setColor(Color.argb(255, 26, 28, 33));
        s.setDistanceBetweenStaveLines(4);//设置五线之间距离
        s.setDistanceBetweenStaves(6);//设置双轨中两轨之间距离
        s.setDistanceBetweenRows(10);//设置轨道之间的距离
        s.setPaddingE(5);
        s.setPaddingS(5);
        s.setPaddingTop(150);
        s.setFirstStaveClef(FM_ClefValue.TREBLE);
        s.setSecondStaveClef(FM_ClefValue.BASS);
        s.setTimeSignature(FM_TimeSignatureValue._4, FM_TimeSignatureValue._4);
        s.setKeySignature(FM_KeySignatureValue.DOb);
        s.setCenterVertical(false);
        s.setStartBar(true);
        s.setEndBar(true);
        s.setMultiRow(true);
        s.setAllowZoomPan(true);
        s.setShowBrace(true);
        s.setTrimLastRow(true);
        s.setNotesAlign(FM_Align.ALIGN_LEFT_LAST_MEASURE);
        s.clearStaveNotes();
        s.setVisibility(View.VISIBLE);
        s.setNoteSpacing(15);
        scoreView = s;

        player = FM_ScorePlayer.getInstance(context.getApplicationContext());
    }
    void ShowScore(Activity context,FM_Score s,String filePath,boolean showRecordTrack){

        JSONObject obj = LoadJson(LoadAssetFile(context,filePath));

        if(obj != null) {
            s.setShowRecordTrack(showRecordTrack);
            s.LoadFromJson(obj);
            player.LoadFromScore(s, 80);
            player.setShowProgress(true);
        }
        else{
            Log.e(TAG, "ShowScore: json obj is null filePath:" + filePath);
        }
    }
    String curShowFile;
    public void ShowScore(Activity context,String filePath,boolean showRecordTrack){
        curShowFile = filePath;
        if(scoreView != null) {
            clearRecordNotes();
            ShowScore(context,scoreView,filePath,showRecordTrack);
        }
        else{
            Log.e(TAG, "ShowScore -> scoreView is null");
        }
    }

    private void clearRecordNotes() {
        scoreView.clearDynamicNotes();
    }

    public void Play(boolean mute, FM_ScorePlayer.OnPlayEnd onPlayEnd) {
        if(player != null) {
            player.Play(mute,onPlayEnd);
            //player.Play(2,2);
        }
        else{
            Log.e(TAG, "Play->Player is null");
        }
    }
    public void Stop(){
        if(player != null) {
            player.StopPlaying();
            //player.Play(2,2);
        }
        else{
            Log.e(TAG, "Stop->Player is null");
        }
    }
    public void updateShowRecordTrack(Activity context,boolean showRecordTrack) {
        ShowScore(context,curShowFile,showRecordTrack);
    }
   /* public void AddNotes(float[] pitch){
        FM_Note[] notes = new FM_Note[pitch.length];
        for (int i=0;i<pitch.length;i++){
            notes[i] = NewNoteWithPitch(pitch[i]);
        }
        this.scoreView.AddDynamicNotes(notes);
    }*/
    public void AddRecordNote(float pitch) {
        if(scoreView.progressBar < 0){
            //Log.e(TAG, "AddNote invalid progressBar:"+scoreView.progressBar);
            return;
        }

        int clef = scoreView.ScoreBase.FirstStaveClef;
        FM_Note newNote = NewNoteWithPitch(clef,pitch);

        if(scoreView.showRecordTrack)
            this.scoreView.AddDynamicNote(newNote,pitch);

        int checkProgressCount = 3;
        if(scoreView.getStaveCount() == FM_StaveCount._2)
            checkProgressCount *= 2;

        int begin = scoreView.progressBar + 1;
        int end = scoreView.progressBar - checkProgressCount * 2;

        int checkedCount = 0;
        for (int i = begin;i>= end;i--) {
            if(i<0 || i>= scoreView.ScoreBase.StaveNotes.size())
                continue;

            FM_BaseNote curNote = scoreView.ScoreBase.StaveNotes.get(i);
            if(curNote instanceof FM_Chord) {
                FM_Chord chord = (FM_Chord)curNote;

                boolean checked = false;
                for (FM_BaseNote curFMNote:chord.Notes) {

                    if(curFMNote instanceof FM_Note){
                        FM_Note fmNote = (FM_Note) curFMNote;
                        if(fmNote != null){
                            if(fmNote.isTieEnd)
                                continue;
                            else if(fmNote.note == 0 && fmNote.octave == 0 && fmNote.getAccidental() == 0)
                                continue;
                        }
                        boolean right = GetKeyWithNote(newNote) == GetKeyWithNote(curFMNote);
                        if(right) {
                            scoreView.AddSign(i,true, curFMNote.getStave());
                        }
                        Log.d(TAG, scoreView.progressBar + "check Note: " + getNoteStr(newNote) + " with progressbar:"+ i + getNoteStr(curFMNote) +"  equal:"+ right);

                        if(!checked){
                            checked = true;
                            checkedCount++;
                        }
                    }
                }
                if(checkedCount >= checkProgressCount)
                    break;
            }
        }
    }
    String getNoteStr(FM_BaseNote newNote){
        return "[note:"+newNote.note + "  oct:" + newNote.octave + " acc:"+newNote.getAccidental()+"]";
    }
    public static int GetKeyWithNote(FM_BaseNote note){
        return GetKeyWithNote(note.note,note.octave,note.getAccidental());
    }
    public static int GetKeyWithNote(int note,int oct,int acc){
        int key = 4 + (oct - 1) * 12 + NoteNAccidental2Index(note,acc);
        return key;
    }
    public static int[] GetNoteWithFrequency(int clef,float frequency){
        // 计算音符的 MIDI 音符
        int midiNote = Frequency2Note(frequency);
        midiNote += 8;
      /*  if(clef == FM_ClefValue.TREBLE)
            midiNote += 8;
        else if(clef == FM_ClefValue.BASS)
            midiNote += 6;*/

        int[] output = new int[3];
        int index = midiNote % 12;

        int[] notesNAcc = Index2NoteNAccidental(index);
        output[0] = notesNAcc[0];
        output[1] = midiNote / 12;
        output[2] = notesNAcc[1];
        //Log.d(TAG, frequency+" midiNote: "+midiNote+"  index:"+index+"  note:"+notesNAcc[0]+"  acc:"+notesNAcc[1]);
        return output;
    }

    public static int[] Index2NoteNAccidental(int index){
        int[] output = new int[2];
/*        if(index == 0) output[0] = 0;output[1] = 0;//40 c
        if(index == 2) output[0] = 0;output[1] = FM_Accidental.Sharp;//42 c#
        if(index == 1) output[0] = 1;output[1] = 0;//41 d
        if(index == 4) output[0] = 1;output[1] = FM_Accidental.Sharp;//44 d#
        if(index == 3) output[0] = 2;output[1] = 0;//43 e
        if(index == 5) output[0] = 3;output[1] = 0;//45 f
        if(index == 7) output[0] = 3;output[1] = FM_Accidental.Sharp;//47 f#
        if(index == 6) output[0] = 4;output[1] = 0;//46 g
        if(index == 9) output[0] = 4;output[1] = FM_Accidental.Sharp;//49 g#
        if(index == 8) output[0] = 5;output[1] = 0;//48 a
        if(index == 11) output[0] = 5;output[1] = FM_Accidental.Sharp;//51 a#
        if(index == 10) output[0] = 6;output[1] = 0;//50 b*/

        if(index == 0) {output[0] = 0;output[1] = 0;}
        if(index == 1) {output[0] = 0;output[1] = FM_Accidental.Sharp;}
        if(index == 2) {output[0] = 1;output[1] = 0;}
        if(index == 3) {output[0] = 1;output[1] = FM_Accidental.Sharp;}
        if(index == 4) {output[0] = 2;output[1] = 0;}
        if(index == 5) {output[0] = 3;output[1] = 0;}
        if(index == 6) {output[0] = 3;output[1] = FM_Accidental.Sharp;}
        if(index == 7) {output[0] = 4;output[1] = 0;}
        if(index == 8) {output[0] = 4;output[1] = FM_Accidental.Sharp;}
        if(index == 9) {output[0] = 5;output[1] = 0;}
        if(index == 10) {output[0] = 5;output[1] = FM_Accidental.Sharp;}
        if(index == 11) {output[0] = 6;output[1] = 0;}
        return output;
    }

    public static int NoteNAccidental2Index(int note, int Accidental){

        int Y1 = FM_NoteValue.DO;
        int Y2 = FM_NoteValue.RE;
        int Y3 = FM_NoteValue.MI;
        int Y4 = FM_NoteValue.FA;
        int Y5 = FM_NoteValue.SOL;
        int Y6 = FM_NoteValue.LA;
        int Y7 = FM_NoteValue.SI;
        //C
        if(note == Y1 && Accidental == FM_Accidental.None) return 0;
        if(note == Y7 && Accidental == FM_Accidental.Sharp) return 0;
        if(note == Y6 && Accidental == FM_Accidental.TripleSharp) return 0;
        if(note == Y2 && Accidental == FM_Accidental.DoubleFlat) return 0;

        //C#
        if(note == Y1 && Accidental == FM_Accidental.Sharp) return 1;
        if(note == Y7 && Accidental == FM_Accidental.DoubleSharp) return 1;
        if(note == Y2 && Accidental == FM_Accidental.Flat) return 1;
        if(note == Y3 && Accidental == FM_Accidental.TripleFlat) return 1;

        //D
        if(note == Y2 && Accidental == FM_Accidental.None)return 2;
        if(note == Y1 && Accidental == FM_Accidental.DoubleSharp) return 2;
        if(note == Y7 && Accidental == FM_Accidental.TripleSharp) return 2;
        if(note == Y3 && Accidental == FM_Accidental.DoubleFlat) return 2;
        if(note == Y4 && Accidental == FM_Accidental.TripleFlat) return 2;

        //D#
        if(note == Y2 && Accidental == FM_Accidental.Sharp)return 3;
        if(note == Y1 && Accidental == FM_Accidental.TripleSharp) return 3;
        if(note == Y3 && Accidental == FM_Accidental.Flat) return 3;
        if(note == Y4 && Accidental == FM_Accidental.DoubleFlat) return 3;

        //E
        if(note == Y3 && Accidental == FM_Accidental.None)return 4;
        if(note == Y2 && Accidental == FM_Accidental.DoubleSharp) return 4;
        if(note == Y4 && Accidental == FM_Accidental.Flat) return 4;
        if(note == Y5 && Accidental == FM_Accidental.TripleFlat) return 4;

        //F
        if(note == Y4 && Accidental == FM_Accidental.None)return 5;
        if(note == Y3 && Accidental == FM_Accidental.Sharp) return 5;
        if(note == Y2 && Accidental == FM_Accidental.TripleSharp) return 5;
        if(note == Y5 && Accidental == FM_Accidental.DoubleFlat) return 5;

        //F#
        if(note == Y4 && Accidental == FM_Accidental.Sharp)return 6;
        if(note == Y3 && Accidental == FM_Accidental.DoubleSharp) return 6;
        if(note == Y5 && Accidental == FM_Accidental.Flat) return 6;
        if(note == Y6 && Accidental == FM_Accidental.TripleFlat) return 6;

        //G
        if(note == Y5 && Accidental == FM_Accidental.None)return 7;
        if(note == Y4 && Accidental == FM_Accidental.DoubleSharp) return 7;
        if(note == Y3 && Accidental == FM_Accidental.TripleSharp) return 7;
        if(note == Y6 && Accidental == FM_Accidental.DoubleFlat) return 7;

        //G#
        if(note == Y5 && Accidental == FM_Accidental.Sharp)return 8;
        if(note == Y4 && Accidental == FM_Accidental.TripleSharp) return 8;
        if(note == Y6 && Accidental == FM_Accidental.Flat) return 8;
        if(note == Y7 && Accidental == FM_Accidental.DoubleFlat) return 8;

        //A
        if(note == Y6 && Accidental == FM_Accidental.None)return 9;
        if(note == Y5 && Accidental == FM_Accidental.DoubleSharp) return 9;
        if(note == Y7 && Accidental == FM_Accidental.Flat) return 9;
        if(note == Y1 && Accidental == FM_Accidental.TripleFlat) return 9;

        //A#
        if(note == Y6 && Accidental == FM_Accidental.Sharp)return 10;
        if(note == Y5 && Accidental == FM_Accidental.TripleSharp) return 10;
        if(note == Y7 && Accidental == FM_Accidental.Flat) return 10;
        if(note == Y1 && Accidental == FM_Accidental.DoubleFlat) return 10;

        //B
        if(note == Y7 && Accidental == FM_Accidental.None)return 11;
        if(note == Y6 && Accidental == FM_Accidental.DoubleSharp) return 11;
        if(note == Y1 && Accidental == FM_Accidental.Flat) return 11;
        if(note == Y2 && Accidental == FM_Accidental.TripleFlat) return 11;

        return 0;
    }

  public   static int Frequency2Note(float frequency){
        double standardFrequency = 440.0;
        double logResult = Math.log(frequency / (standardFrequency / 32.0)) / Math.log(2);
        double note = 12.0 * logResult + 9.0 - 20;
        int output = (int)(note+0.5f);
        //Log.d(TAG, "频率 " + frequency+ "Hz 对应的音高为 " + note);
        return output;
    }

   public static float Note2Frequency(int note){
        double standardFrequency = 440.0;
        double frequency = (standardFrequency / 32.0) * Math.pow(2, (note + 20 - 9.0) / 12.0);
        Log.d(TAG, "音高 " + note + " 对应的频率为 " + frequency + " Hz");
        return (float)frequency;
    }

    FM_Note NewNoteWithPitch(int clef,float pitch){
        int[] notes = GetNoteWithFrequency(clef,pitch);
        int Note = notes[0];
        int Octave = notes[1];
        int Accidental = notes[2];
        int key = Octave*12 + NoteNAccidental2Index(Note,Accidental);
        //Log.d(TAG, pitch+" NewNoteWithPitch note: "+Note+" Octave"+Octave+" addedNoteCount:"+scoreView.addedNoteCount +"  count key:"+ key);
        int Duration = FM_DurationValue.NOTE_EIGHTH;
        int voice = 0;
        boolean StemUp = true;
        FM_Note n = new FM_Note(this.scoreView.dynamicScore,Note, Octave, clef, Accidental, Duration, voice, StemUp);
        return n;
    }

    @SuppressLint("WrongConstant")
    public void OnClickBBox(){
        if (scoreView != null) {
            int bbIndex = scoreView.getShowBoundingBoxes();
            bbIndex = bbIndex + 1;
            if (bbIndex > 2) bbIndex = 0;
            scoreView.ShowBoundingBoxes(bbIndex);
            scoreView.setAllowZoomControls(! scoreView.isAllowZoomControls());
        }

    }


    public String LoadAssetFile(Activity context,String path){

        BufferedReader reader = null;
        StringBuilder stringBuilder = new StringBuilder();
        try
        {
            AssetManager assetManager = context.getAssets();
            /*String fileNames[] = assetManager.list("");
            Log.d(TAG, "LoadAssetFile fileNames length: "+fileNames.length);
            for (String f : fileNames) {
                Log.d(TAG, "LoadAssetFile -> path file: "+f);
            }*/
            InputStream input = assetManager.open(path);
            reader = new BufferedReader(new InputStreamReader(input));
            String mLine;
            while ((mLine = reader.readLine()) != null) {
                stringBuilder.append(mLine);
            }
        }
        catch (IOException e) {
            Log.e(TAG, "LoadAssetFile error: "+e);
        }
        finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    //log the exception
                    Log.e(TAG, "LoadAssetFile reader error: "+e.toString());
                }
            }
        }
        String content =  stringBuilder.toString();
        //Log.d(TAG, "LoadAssetFile: "+content);
        return content;
    }

    JSONObject LoadJson(String text){

        JSONObject obj = null;
        try {
            obj = new JSONObject(text);
        } catch (Exception ex) {
            Log.e(TAG, "LoadJson: "+ex);
        }
        return obj;
    }

}
